// Copyright (c) 2020 Presto Labs Pte. Ltd.
// Author: daniel

#include "coin2/exchange/gdax/api_order/fix/fix_message_builder.h"

#include <utility>

#include <glog/logging.h>

#include "coin2/exchange/base/order/order.h"
#include "coin2/exchange/base/symbology/product.h"
#include "coin2/exchange/base/symbology/product_info.h"
#include "coin2/exchange/gdax/api_order/fix/fix_fields.h"
#include "coin2/exchange/gdax/symbology/product.h"
#include "quickfix/fix42/NewOrderSingle.h"
#include "quickfix/fix42/OrderCancelRequest.h"
#include "quickfix/fix42/OrderStatusRequest.h"

namespace coin2::exchange::gdax::api_order::fix {

GdaxOrder::GdaxOrder(const OrderExecution& ord_exec, GetProductHolderFunc get_product_holder) {
  Parse(ord_exec, get_product_holder);
  Validate();
}

void GdaxOrder::Parse(const OrderExecution& ord_exec, GetProductHolderFunc get_product_holder) {
  using OrderType = coin2::exchange::base::order::OrderType;

  using GdaxProduct = coin2::exchange::gdax::symbology::GdaxProduct;
  auto product =
      GdaxProduct::FromStr(ord_exec.order_spec->product, ord_exec.order_spec->order_created_time);
  auto product_holder = get_product_holder(product, ord_exec.order_spec->order_created_time);
  auto formatter = product_holder->product_info().order_formatter();

  client_id = std::to_string(ord_exec.proc_order_id);
  symbol = ord_exec.order_spec->product;
  quantity = stod(formatter.FormatQty(ord_exec.order_spec->qty));
  price = stod(formatter.FormatPrice(ord_exec.order_spec->price));
  type = ord_exec.order_spec->order_type == OrderType::MARKET_ORDER ? MARKET : LIMIT;
  side = ord_exec.order_spec->order_side == 1 ? BUY : SELL;
  handlinst = AUTOMATED_EXECUTION_NO_INTERVENTION;

  // TODO(daniel)
  self_trade_prevention = SPACE;
  cash_order_qty = 0;
  stop_price = 0;

  time_in_force = GOOD_TILL_CANCEL;
  if (ord_exec.order_spec->post_only) {
    time_in_force = POST_ONLY;
  } else if (ord_exec.order_spec->duration == coin::proto::OrderDuration::FOK_ORDER) {
    time_in_force = FILL_OR_KILL;
  } else if (ord_exec.order_spec->duration == coin::proto::OrderDuration::IOC_ORDER) {
    time_in_force = IMMEDIATE_OR_CANCEL;
  } else {
    time_in_force = GOOD_TILL_CANCEL;
  }
}

void GdaxOrder::Validate() const {
  // TODO(daniel) - validate fields
}

auto FixMessageBuilder::GeneratePlaceOrder(
    const OrderExecution& ord_exec,
    GetProductHolderFunc get_product_holder) -> RequestTypePtr {
  GdaxOrder order{ord_exec, get_product_holder};
  RequestTypePtr message = std::make_unique<FIX42::NewOrderSingle>();
  message->setField(FIX::HandlInst(order.handlinst));
  message->setField(FIX::ClOrdID(new_order_prefix_of_client_id_ + order.client_id));
  message->setField(FIX::Symbol(order.symbol));
  message->setField(FIX::Side(order.side));
  message->setField(FIX::OrderQty(order.quantity));
  message->setField(FIX::OrdType(order.type));
  if (MARKET == order.type) {
    // message->setField(FIX::CashOrderQty(cash_order_qty));
  } else if (LIMIT == order.type) {
    message->setField(FIX::Price(order.price));
    message->setField(FIX::TimeInForce(order.time_in_force));
  } else if (STOP_LIMIT == order.type) {
    // message->setField(FIX::StopPx(stop_price));
  }
  if (order.self_trade_prevention != SPACE) {
    message->setField(FIX::SelfTradePrevention(order.self_trade_prevention));
  }

  return message;
}

auto FixMessageBuilder::GenerateCancelOrder(const OrderExecution& ord_exec) -> RequestTypePtr {
  const std::string order_id_str = ord_exec.oc == nullptr ? "" : ord_exec.oc->exchange_order_id();
  const std::string client_id_str = ProcessClientOrderID(ord_exec.proc_order_id);
  const std::string symbol = ord_exec.oc == nullptr ? "" : ord_exec.oc->order_spec().product;
  RequestTypePtr message = std::make_unique<FIX42::OrderCancelRequest>();
  // message->setField(FIX::OrigClOrdID(client_id_str));
  message->setField(FIX::ClOrdID(client_id_str));
  message->setField(FIX::OrderID(order_id_str));
  message->setField(FIX::Symbol(symbol));
  // message->setField(FIX::Text(client_id_str));

  return message;
}

auto FixMessageBuilder::GenerateQueryOrder(const OrderExecution& ord_exec) -> RequestTypePtr {
  const std::string order_id_str = ord_exec.oc == nullptr ? "" : ord_exec.oc->exchange_order_id();
  const std::string client_id_str = ProcessClientOrderID(ord_exec.proc_order_id);
  RequestTypePtr message = std::make_unique<FIX42::OrderStatusRequest>();
  if (!client_id_str.empty()) {
    message->setField(FIX::OrigClOrdID(client_id_str));
  }
  if (!order_id_str.empty()) {
    message->setField(FIX::OrderID(order_id_str));
  }

  return message;
}

std::string FixMessageBuilder::ProcessClientOrderID(int64_t proc_order_id) {
  if (0 == proc_order_id) {
    return std::string{};
  }
  auto tmp_str = std::to_string(proc_order_id);
  tmp_str.insert(7, "-");
  tmp_str.insert(3, "-");
  return cancel_order_prefix_of_client_id_ + tmp_str;
}

}  // namespace coin2::exchange::gdax::api_order::fix
